/*
 * The contents of this web application are subject to the Mozilla Public License Version 
 * 1.1 (the "License"); you may not use this web application except in compliance with 
 * the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/.
 * 
 * Software distributed under the License is distributed on an "AS IS" basis, 
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 
 * for the specific language governing rights and limitations under the License.
 * 
 * The Original Code is owned by and the Initial Developer of the Original Code is 
 * Composite A/S (Danish business reg.no. 21744409). All Rights Reserved
 * 
 * Section 11 of the License is EXPRESSLY amended to include a provision stating 
 * that any dispute, including but not limited to disputes related to the enforcement 
 * of the License, to which Composite A/S as owner of the Original Code, as Initial 
 * Developer or in any other role, becomes a part to shall be governed by Danish law 
 * and be initiated before the Copenhagen City Court ("K�benhavns Byret")            
 */

using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;


namespace Composite.Core.Linq
{
    internal class ExpressionBuilder
    {
        private Type _currentQueryableType;
        private readonly IQueryable _sourceQueryable;
        private Expression _currentExpression;

        private static readonly PropertyInfo _dateDateTimePropertyInfo = typeof(DateTime).GetProperty("Date");


        public ExpressionBuilder(Type queryableType, IQueryable sourceQueryable)
        {

            _sourceQueryable = sourceQueryable;
            _currentExpression = _sourceQueryable.Expression;
            _currentQueryableType = queryableType;
        }


        public Type QueryableType { get; private set; }



        public ExpressionBuilder Where(PropertyInfoValueCollection propertyInfoValueCollection)
        {
            return Where(propertyInfoValueCollection, false);
        }



        public ExpressionBuilder Where(PropertyInfoValueCollection propertyInfoValueCollection, bool useInnerDateTimeDate)
        {
            ParameterExpression parameterExpression = Expression.Parameter(_currentQueryableType, "w");

            Expression currentExpression = null;
            foreach (var kvp in propertyInfoValueCollection.PropertyValues)
            {
                Expression left = LambdaExpression.Property(parameterExpression, kvp.Key);                
                Expression right = Expression.Constant(kvp.Value, kvp.Key.PropertyType);

                if (useInnerDateTimeDate && kvp.Key.PropertyType == typeof(DateTime))
                {
                    left = InnerDateTimeDateExpression(left);
                    right = InnerDateTimeDateExpression(right);
                }

                Expression filter = Expression.Equal(left, right);

                if (currentExpression == null)
                {
                    currentExpression = filter;
                }
                else
                {
                    currentExpression = Expression.And(currentExpression, filter);
                }
            }

            LambdaExpression lambdaExpression = Expression.Lambda(currentExpression, parameterExpression);

            MethodCallExpression methodCallExpression = Expression.Call
            (
                typeof(Queryable),
                "Where",
                new Type[] { _currentQueryableType, },
                _currentExpression,
                Expression.Quote(lambdaExpression)
            );

            _currentExpression = methodCallExpression;

            return this;
        }



        public ExpressionBuilder OrderBy(PropertyInfo orderByPropertyInfo, bool useInnerDateTimeDate, bool orderDescending)
        {
            ParameterExpression parameter = Expression.Parameter(_currentQueryableType, "o");

            Expression expression = Expression.Property(parameter, orderByPropertyInfo);
            if ((useInnerDateTimeDate) && (orderByPropertyInfo.PropertyType == typeof(DateTime)))
            {
                expression = InnerDateTimeDateExpression(expression);
            }


            LambdaExpression lambdaExpression = Expression.Lambda(expression, parameter);

            MethodCallExpression methodCallExpression = Expression.Call
            (
                typeof(Queryable),
                (orderDescending ? "OrderByDescending" : "OrderBy"),
                new Type[] { _currentQueryableType, orderByPropertyInfo.PropertyType },
                _currentExpression,
                Expression.Quote(lambdaExpression)
            );

            _currentExpression = methodCallExpression;

            return this;
        }



        public ExpressionBuilder Select(PropertyInfo selectPropertyInfo)
        {
            return Select(selectPropertyInfo, false);
        }



        public ExpressionBuilder Select(PropertyInfo selectPropertyInfo, bool useInnerDateTimeDate)
        {
            ParameterExpression parameter = Expression.Parameter(_currentQueryableType, "s");

            Expression expression = Expression.Property(parameter, selectPropertyInfo);
            if (useInnerDateTimeDate && selectPropertyInfo.PropertyType == typeof(DateTime))
            {
                expression = InnerDateTimeDateExpression(expression);
            }

            LambdaExpression lambdaExpression = Expression.Lambda(expression, parameter);

            MethodCallExpression methodCallExpression = Expression.Call
            (
                typeof(Queryable),
                "Select",
                new Type[] { _currentQueryableType, selectPropertyInfo.PropertyType },
                _currentExpression,
                Expression.Quote(lambdaExpression)
            );

            _currentExpression = methodCallExpression;
            _currentQueryableType = selectPropertyInfo.PropertyType;

            return this;
        }



        public ExpressionBuilder Distinct()
        {
            MethodCallExpression methodCallExpression = Expression.Call
                (
                    typeof(Queryable),
                    "Distinct",
                    new Type[] { _currentQueryableType },
                    _currentExpression
                );

            _currentExpression = methodCallExpression;

            return this;
        }



        public IQueryable CreateQuery()
        {
            return _sourceQueryable.Provider.CreateQuery(_currentExpression);
        }



        private Expression InnerDateTimeDateExpression(Expression expression)
        {
            return Expression.Property(expression, _dateDateTimePropertyInfo);
        }
    }
}
